<html>
    <head>
        <title>Asp.net core 视图组件  ViewComponent</title>
    </head>
    <body>
        <script>
            // Asp.net core  视图组件  ViewComponent 

                    // https://learn.microsoft.com/zh-cn/aspnet/core/mvc/views/view-components?view=aspnetcore-9.0

                    /*

                        一、视图组件概览

                                    视图组件与分部视图类似，但它们的功能更加强大。

                                    视图组件不使用模型绑定，它们依赖于调用视图组件时传递的数据。

                                    本文是使用控制器和视图编写的，但视图组件适用于 Razor Pages。

                                    1、视图组件特点：

                                                视图组件：

                                                        ①、呈现一个区块而不是整个响应。

                                                        ②、包括控制器和视图间发现的相同关注点分离和可测试性优势。

                                                        ③、可以有参数和业务逻辑。

                                                        ④、通常从布局页调用。

                                    2、使用场景
                                                视图组件可用于具有可重用呈现逻辑（对部分视图来说过于复杂）的任何位置，
                                                
                                                    eg：    动态导航菜单
                                                        标记云，查询数据库的位置
                                                        登录面板
                                                        购物车
                                                        最近发布的文章
                                                        博客上的边栏内容
                                                        一个登录面板，呈现在每页上并显示注销或登录链接，具体取决于用户的登录状态


                                    3、视图组件的构成

                                            ①、类，通常派生自 ViewComponent

                                            ②、它返回的结果，通常是视图。


                                        与控制器一样，视图组件可以是 POCO，但大多数开发人员都利用派生自 ViewComponent 的可用方法和属性。
                    */

                    /*
                        二、创建视图组件


                                    本部分包含创建视图组件的高级别要求。 本文后续部分将详细检查每个步骤并创建视图组件。


                                    1、视图组件类

                                               可通过以下任一方法创建视图组件类：

                                                    ①、派生自 ViewComponent

                                                    ②、使用 [ViewComponent] 属性修饰类，或者从具有 [ViewComponent] 属性的类派生

                                                    ③、创建名称以 ViewComponent 后缀结尾的类ViewComponent

                                                与控制器一样，视图组件必须是 "公共的"、"非嵌套的" 和 "非抽象的"的类。

                                                视图组件名称是删除了 ViewComponent后缀的类名。

                                                也可以使用 [ViewComponent(Name = "组件名称")] 属性来重命名视图组件。

                                                ★、视图组件类：

                                                        ①、支持构造函数依赖项注入

                                                        ②、不参与控制器生命周期，因此不能在视图组件中使用筛选器


                                                ★、要阻止将具有 ViewComponent 后缀（不区分大小写）的类视为视图组件，请使用 [NonViewComponent] 特性修饰该类：

                                                        using Microsoft.AspNetCore.Mvc;

                                                        [NonViewComponent]
                                                        public class ReviewComponent
                                                        {
                                                            public string Status(string name) => JobStatus.GetCurrentStatus(name);
                                                        }


                                    2、视图组件方法：

                                                视图组件在以下方法中定义其逻辑：

                                                        ①、返回 Task<IViewComponentResult> 的 InvokeAsync 方法。、
                                                        
                                                        ②、返回 IViewComponentResult 的 Invoke 同步方法。


                                                参数直接来自视图组件的调用，而不是来之模型绑定。

                                                视图组件从来不直接处理请求。通常，视图组件通过调用View方法来初始化模型并将其并将其传递到视图。

                                                总之，视图组件方法：

                                                        ①、定义返回 Task<IViewComponentResult> 的 InvokeAsync 方法，或是返回 IViewComponentResult 的同步 Invoke 方法。

                                                        ②、通常，通过调用 ViewComponent.View 方法来初始化模型并将其传递到视图。

                                                        ③、参数来自调用方法，而不是 HTTP。 没有模型绑定。
                                                        
                                                        ④、不可直接作为 HTTP 终结点进行访问。 它们通常在视图中调用。 视图组件从不处理请求。

                                                        ⑤、在签名上重载，而不是当前 HTTP 请求的任何详细信息。


                                    3、视图搜索路径：


                                                 /Views/{Controller Name}/Components/{View Component Name}/{View Name} (MVC模式下)
                                                 /Views/Shared/Components/{View Component Name}/{View Name} (MVC模式下)
                                                 /Pages/Shared/Components/{View Component Name}/{View Name} (RazorPages模式下)

                                                搜索路径适用于使用控制器 + 视图和 Razor Pages 的项目。

                                                视图组件的默认视图为Default,这意味着视图组件的视图文件名通常为  Default.cshtml.

                                                可以在创建视图组件结果或调用 View 方法时指定不同的视图名称。

                                                建议将视图文件命名为 Default.cshtml 并使用 Views/Shared/Components/{View Component Name}/{View Name} 路径。

                                                此示例中使用的 PriorityList 视图组件对视图组件视图使用 Views/Shared/Components/PriorityList/Default.cshtml。


                                    4、自定义视图搜索路径

                                                修改 Razor 的 ViewLocationFormats (视图位置格式)集合，以自定义视图搜索路径。

                                                 例如，将新项添加到集合，以搜索路径 /Components/{View Component Name}/{View Name} 中的视图：

                                                        using Microsoft.EntityFrameworkCore;
                                                        using ViewComponentSample.Models;

                                                        var builder = WebApplication.CreateBuilder(args);

                                                        builder.Services.AddControllersWithViews()
                                                            .AddRazorOptions(options =>
                                                            {
                                                                options.ViewLocationFormats.Add("/{0}.cshtml");  // 视图位置格式集合
                                                            });

                                                        builder.Services.AddDbContext<ToDoContext>(options =>
                                                                options.UseInMemoryDatabase("db"));

                                                        var app = builder.Build();

                                                     在前面的代码中，占位符 {0} 表示路径 Components/{View Component Name}/{View Name}。

                           */


                           /*
                                三、调用视图组件

                                                 1、razor语法调用视图组件

                                                            要使用视图组件，请在视图中调用以下内容：

                                                                        @await Component.InvokeAsync("Name of view component",
                                                                                                    {Anonymous Type Containing Parameters})

                                                            参数将传递给 InvokeAsync 方法。

                                                            本文中开发的 PriorityList 视图组件调用自 Views/ToDo/Index.cshtml 视图文件。 
                                                            
                                                            在以下代码中，使用两个参数调用 InvokeAsync 方法：

                                                                    <div>
                                                                        Maximum Priority: @ViewData["maxPriority"] <br />
                                                                        Is Complete:  @ViewData["isDone"]
                                                                        @await Component.InvokeAsync("PriorityList",
                                                                                        new { 
                                                                                            maxPriority =  ViewData["maxPriority"],
                                                                                            isDone = ViewData["isDone"]  }
                                                                                        )
                                                                    </div>      

                                                2、使用标签助手调用视图组件

                                                            视图组件可以使用标签助手调用：

                                                                    <div>
                                                                        Maxium Priority: @ViewData["maxPriority"] <br />
                                                                        Is Complete:  @ViewData["isDone"]
                                                                        @{
                                                                            int maxPriority = Convert.ToInt32(ViewData["maxPriority"]);
                                                                            bool isDone = Convert.ToBoolean(ViewData["isDone"]);
                                                                        }
                                                                        <vc:priority-list max-priority=maxPriority is-done=isDone>
                                                                        </vc:priority-list>
                                                                    </div>


                                                             标签助手采用 Pascal 大小写格式的类和方法参数将转换为各自相应的短横线格式。 
                                                             
                                                             要使用标签助手调用视图组组件使用 <vc></vc> 元素。 按如下方式指定视图组件：
                                                              
                                                                    <vc:[view-component-name]
                                                                            parameter1="parameter1 value"
                                                                            parameter2="parameter2 value">
                                                                    </vc:[view-component-name]>

                                                            若要使用标签助手调用视图组件，请使用 @addTagHelper 指令注册包含视图组件的程序集。

                                                            例如视图组件位于名为 MyWebApp 的程序集中，请将以下指令添加到 _ViewImports.cshtml 文件：

                                                                    @addTagHelper *, MyWebApp

                                                            本教程中使用的 InvokeAsync 方法：

                                                                    @await Component.InvokeAsync("PriorityList",
                                                                        new { 
                                                                            maxPriority =  ViewData["maxPriority"],
                                                                            isDone = ViewData["isDone"]  }
                                                                    )
                    */

                    /*
                        四、创建基本视图组件   


                                       1、更新控制器以传入优先级和完成状态

                                                        更新 Index 方法以使用优先级和完成状态参数：
                                                        
                                                            using Microsoft.AspNetCore.Mvc;
                                                            using ViewComponentSample.Models;

                                                            namespace ViewComponentSample.Controllers;
                                                            public class ToDoController : Controller
                                                            {
                                                                private readonly ToDoContext _ToDoContext;

                                                                public ToDoController(ToDoContext context)
                                                                {
                                                                    _ToDoContext = context;
                                                                    _ToDoContext.Database.EnsureCreated();
                                                                }

                                                                // 在Index视图中，通过model传值
                                                                public IActionResult Index(int maxPriority = 2, bool isDone = false)
                                                                {
                                                                    var model = _ToDoContext!.ToDo!.ToList();
                                                                    ViewData["maxPriority"] = maxPriority;
                                                                    ViewData["isDone"] = isDone;
                                                                    return View(model); // 如果没有视图名称，则使用Default.cshtml作为视图名称。
                                                                } 


                                       2、添加ViewComponent类

                                                        将 ViewComponent 类添加到 ViewComponents/PriorityListViewComponent.cs：
                                                        
                                                                using Microsoft.AspNetCore.Mvc;
                                                                using Microsoft.EntityFrameworkCore;
                                                                using ViewComponentSample.Models;

                                                                namespace ViewComponentSample.ViewComponents;

                                                                public class PriorityListViewComponent : ViewComponent  // 创建视图组件类。
                                                                {
                                                                    private readonly ToDoContext db;

                                                                    // 构造函数注入 ToDoContext 实例。
                                                                    public PriorityListViewComponent(ToDoContext context) => db = context;

                                                                    // 实现 InvokeAsync 方法。（调用方法）
                                                                    public async Task<IViewComponentResult> InvokeAsync( int maxPriority, bool isDone)  // 自己可以给此函数定义任意数量参数
                                                                    {
                                                                        var items = await GetItemsAsync(maxPriority, isDone);
                                                                        return View(items);  // 通过ViewModel 传值，没有视图名称参数，则使用Default.cshtml作为视图名称。

                                                                    }

                                                                    // 私有方法
                                                                    private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone)
                                                                    {
                                                                        return db!.ToDo!.Where(x => x.IsDone == isDone &&
                                                                                            x.Priority <= maxPriority).ToListAsync();
                                                                    }
                                                                }

                                                        ★、代码说明：
                                                                    ①、视图组件类可以包含在项目的任意文件夹中。

                                                                    ②、因为类名 PriorityListViewComponent 以后缀 ViewComponent 结尾，所以运行时将在从视图引用类组件时使用字符串 "PriorityList" 作为名称。

                                                                    ③、[ViewComponent] 特性可以更改用于引用视图组件的名称。 、

                                                                                例如，类可以使用以下 [ViewComponent] 属性命名为 XYZ：

                                                                                    [ViewComponent(Name = "PriorityList")]
                                                                                    public class XYZ : ViewComponent

                                                                                上述代码中的[ViewComponent]特性告知视图组件选择器：

                                                                                    -1- 查找与组件相关联的视图时，视图的名称为 PriorityList。

                                                                                    -2- 从视图中引用类组件时的字符串为 "PriorityList"

                                                                    ④、组件使用依赖注入以使数据上下文可用

                                                                    ⑤、InvokeAsync 公开可以从视图调用的方法，且可以采用任意数量的参数。

                                                                    ⑥、InvokeAsync 方法返回满足 isDone 和 maxPriority 参数的 ToDo 项集。


                                        3、创建视图组件的 Razor 视图     
                                        
                                                    ①、创建 Views/Shared/Components 文件夹。 此文件夹 必须 命名为 Components。
                                                    
                                                    ②、创建 Views/Shared/Components/PriorityList 文件夹。

                                                                      此文件夹名称必须与视图组件类的名称相匹配，或者与类的名称减去后缀相匹配。

                                                                      如果使用了 ViewComponent 属性，则类名称需要匹配指定的属性。
                                                            
                                                    ③、创建  Views/Shared/Components/PriorityList/Default.cshtml  Razor视图文件：

                                                        eg:
                                                                @model IEnumerable<ViewComponentSample.Models.TodoItem>

                                                                <h3>Priority Items</h3>
                                                                <ul>
                                                                    @foreach (var todo in Model)
                                                                    {
                                                                        <li>@todo.Name</li>
                                                                    }
                                                                </ul>  

                                                            Razor 视图获取并显示TodoItem列表。如果视图组件 InvokeAsync 方法不传递视图名称，则an照约定使用 Default.cshtml 作为视图。

                                                            要替代特定控制器的默认样式，请将视图添加到控制器特定的视图文件夹
                                                            
                                                                （例如 Views/ToDo/Components/PriorityList/Default.cshtml）。

                                                            如果视图组件特定于控制器，则可以将其添加到特定于控制器的文件夹。 
                                                            
                                                                例如，Views/ToDo/Components/PriorityList/Default.cshtml 是特定于控制器的。
                                                            


                                                    ④、将包含优先级列表组件调用的 div 添加到 Views/ToDo/index.cshtml 文件底部：
                                                            
                                                                <div>
                                                                    Maximum Priority: @ViewData["maxPriority"] <br />
                                                                    Is Complete:  @ViewData["isDone"]
                                                                    @await Component.InvokeAsync("PriorityList",
                                                                                    new { 
                                                                                        maxPriority =  ViewData["maxPriority"],
                                                                                        isDone = ViewData["isDone"]  }
                                                                                    )
                                                                </div>

                                                            标记 @await Component.InvokeAsync 显示调用视图组件的语法。

                                                            第一个参数是要调用的组件的名称。后续参数将传递给该组件。 InvokeAsync 可以采用任意数量的参数。

                                                            
                                                            可以直接从控制器调用视图组件：

                                                                public IActionResult IndexVC(int maxPriority = 2, bool isDone = false)
                                                                {
                                                                    return ViewComponent("PriorityList",
                                                                        new { 
                                                                        maxPriority = maxPriority,
                                                                        isDone = isDone
                                                                        });
                                                                }
                                    4、指定视图组件名称
                                    
                                                在某些情况下，复杂的视图组件可能需要指定非默认视图。

                                                以下代码显示如何从 InvokeAsync 方法指定“PVC”视图。

                                                更新 PriorityListViewComponent 类中的 InvokeAsync 方法：

                                                    public async Task<IViewComponentResult> InvokeAsync( int maxPriority, bool isDone)
                                                    {
                                                        string MyView = "Default";
                                                        // If asking for all completed tasks, render with the "PVC" view.
                                                        if (maxPriority > 3 && isDone == true)  // 根据"优先级"和"是否完成"来决定是使用哪个视图。
                                                        {
                                                            MyView = "PVC";　// 根据情况选择为组件返回不同的视图
                                                        }
                                                        var items = await GetItemsAsync(maxPriority, isDone);
                                                        return View(MyView, items);  // 有视图名称参数，就使用指定视图
                                                    }    
                                                        
                                                将 Views/Shared/Components/PriorityList/Default.cshtml 文件复制到名为

                                                Views/Shared/Components/PriorityList/PVC.cshtml 的视图。 添加标题以指示正在使用 PVC 视图：

                                                        @model IEnumerable<ViewComponentSample.Models.TodoItem>

                                                        <h2> PVC Named Priority Component View</h2>
                                                        <h4>@ViewBag.PriorityMessage</h4>
                                                        <ul>
                                                            @foreach (var todo in Model)
                                                            {
                                                                <li>@todo.Name</li>
                                                            }
                                                        </ul> 
                                                                                                                        
                                                       
                                  5、检查视图路径

                                            暂时将 Views/ToDo/Components/PriorityList/Default.cshtml 重命名为 1Default.cshtml。  

                                            测试应用，出现以下错误：
                                            
                                            error:

                                                 "
                                                        An unhandled exception occurred while processing the request.
                                                        InvalidOperationException: The view 'Components/PriorityList/Default' wasn't found. The following locations were searched:
                                                        /Views/ToDo/Components/PriorityList/Default.cshtml
                                                        /Views/Shared/Components/PriorityList/Default.cshtml
                                                 "

                                  6、避免使用硬编码字符串

                                            为确保编译时安全性，请将硬编码的视图组件名称替换为类名。

                                            将 PriorityListViewComponent.cs 文件更新为不使用“ViewComponent”后缀：

                                                        using Microsoft.AspNetCore.Mvc;
                                                        using Microsoft.EntityFrameworkCore;
                                                        using ViewComponentSample.Models;

                                                        namespace ViewComponentSample.ViewComponents;

                                                        public class PriorityList : ViewComponent
                                                        {
                                                            private readonly ToDoContext db;

                                                            public PriorityList(ToDoContext context)
                                                            {
                                                                db = context;
                                                            }

                                                            public async Task<IViewComponentResult> InvokeAsync(
                                                                                                    int maxPriority, bool isDone)
                                                            {
                                                                var items = await GetItemsAsync(maxPriority, isDone);
                                                                return View(items);
                                                            }

                                                            private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone)
                                                            {
                                                                return db!.ToDo!.Where(x => x.IsDone == isDone &&
                                                                                    x.Priority <= maxPriority).ToListAsync();
                                                            }
                                                        }


                                            视图文件：

                                                    <div>
                                                        Testing nameof(PriorityList) <br />

                                                        Maxium Priority: @ViewData["maxPriority"] <br />
                                                        Is Complete:  @ViewData["isDone"]
                                                        @await Component.InvokeAsync(nameof(PriorityList),  // 使用组件类名
                                                                        new { 
                                                                            maxPriority =  ViewData["maxPriority"],
                                                                            isDone = ViewData["isDone"]  }
                                                                        )
                                                    </div>

                                                    采用CLRLEI类型的 Component.InvokeAsync 方法重载使用 typeof 运算符：、

                                                       <div>
                                                            Testing typeof(PriorityList) <br />

                                                            Maxium Priority: @ViewData["maxPriority"] <br />
                                                            Is Complete:  @ViewData["isDone"]
                                                            @await Component.InvokeAsync(typeof(PriorityList),  // Type类型参数
                                                                            new { 
                                                                                maxPriority =  ViewData["maxPriority"],
                                                                                isDone = ViewData["isDone"]  }
                                                                            )
                                                        </div>



                                  7、执行同步工作

                                                如果不需要异步工作，框架将处理调用同步Involke方法。

                                                以下方法将创建同步Invoke视图组件：

                                                        using Microsoft.AspNetCore.Mvc;
                                                        using ViewComponentSample.Models;

                                                        namespace ViewComponentSample.ViewComponents
                                                        {
                                                            public class PriorityListSync : ViewComponent
                                                            {
                                                                private readonly ToDoContext db;

                                                                public PriorityListSync(ToDoContext context)
                                                                {
                                                                    db = context;
                                                                }

                                                                public IViewComponentResult Invoke(int maxPriority, bool isDone)
                                                                {
                                                        
                                                                    var x = db!.ToDo!.Where(x => x.IsDone == isDone &&
                                                                                        x.Priority <= maxPriority).ToList();
                                                                    return View(x);
                                                                }
                                                            }
                                                        }

                                                视图组件的 Razor文件：

                                                        <div>
                                                            Testing nameof(PriorityList) <br />

                                                            Maxium Priority: @ViewData["maxPriority"] <br />
                                                            Is Complete:  @ViewData["isDone"]
                                                            @await Component.InvokeAsync(nameof(PriorityListSync),
                                                                            new { 
                                                                                maxPriority =  ViewData["maxPriority"],
                                                                                isDone = ViewData["isDone"]  }
                                                                            )
                                                        </div>

                                                使用以下方法之一在 Razor 文件（例如 Views/Home/Index.cshtml）中调用视图组件：

                                                        ①、IViewComponentHelper

                                                        ②、标签助手

                                                        若要使用 IViewComponentHelper 方法，请调用 Component.InvokeAsync：

                                                                @await Component.InvokeAsync(nameof(PriorityList),new { maxPriority = 4, isDone = true })

                                                        若要使用标签助手，请使用 @addTagHelper 指令注册包含视图组件的程序集（视图组件位于名为 MyWebApp 的程序集中）：

                                                                @addTagHelper *, MyWebApp

                                                        然后，在 Razor 标记文件中使用视图组件标记帮助程序：

                                                                <vc:priority-list max-priority="999" is-done="false">
                                                                </vc:priority-list>


                                                （PriorityList.Invoke 的方法签名是同步的，但 Razor 在标记文件中使用 Component.InvokeAsync 找到并调用该方法。）

                    */

        </script>
    </body>
</html>